package com.lifesting.tool.j2as3.visitor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.util.IModifierConstants;

import com.lifesting.tool.j2as3.action.Util;

/**
 * Scan constructors for merging them all into a single constructor method,
 * because ActionScript3 only support one.
 * 
 * @author David BY Chan
 * 
 */
public class OverLoadMethodVisitor extends ASTVisitor {
	/*static class Entry {
		String[] parameterTypes;
		
		public Entry(String[] parameterTypes) {
			super();
			this.parameterTypes = parameterTypes;
		}

		@Override
		public String toString() {
			StringBuffer sb = new StringBuffer();
			sb.append('(');
			int ct = 0;
			for (String p : parameterTypes) {
				if (ct++ > 0)
					sb.append(',');
				sb.append(p);
			}
			sb.append(')');
			return sb.toString();
		}	
	}
	public static class ConstructorDescriptor {
		List<Entry> entries = new ArrayList<Entry>();
		public int queryIndex(String[] pts){
			for (int i = 0; i < entries.size(); i++)
			{
				if (Arrays.equals(entries.get(i).parameterTypes, pts)){
					return i;
				}
			}
			return -1;
		}
		
		public ConstructorDescriptor(List<Entry> entries) {
			super();
			this.entries = entries;
		}

		void addAllConstructors(List<Entry> es){
			entries.addAll(es);
		}
		@Override
		public String toString() {
			StringBuffer buffer = new StringBuffer();
			for (int i = 0; i < entries.size() ; i++){
				buffer.append(i).append(':').append(entries.get(i).toString()).append('\n');
			}
			return buffer.toString();
		}
	}

	private static class ConstructorCounter {
		String qn;
		List<Entry> entries = new ArrayList<Entry>();
		public ConstructorCounter(String qn) {
			super();
			this.qn = qn;
		}
		public void addNewConstructor(String[] pts){
			entries.add(new Entry(pts));
		}

	}*/
	class MethodGroup{
		String name;
		List<MethodDeclaration> methods = new ArrayList<MethodDeclaration>();
		MethodGroup(String name, MethodDeclaration d) {
			this.name = name;
			methods.add(d);
		}
		
	}
	class MethodFrame{
		TypeDeclaration type;
		List<MethodGroup> groups = new ArrayList<MethodGroup>();
		MethodFrame(TypeDeclaration type) {
			this.type = type;
		}
		
		void addMethod(MethodDeclaration method){
			String name = method.getName().getIdentifier();
			for (MethodGroup g : groups){
				if (g.name.equals(name)){
					g.methods.add(method);
					return;
				}
			}
			groups.add(new MethodGroup(name,method));
		}
	}
	// Store class whose constructor number exceeds one.
	//Method key, realName
	private List<OverloadMethodDescriptor> descriptors = new ArrayList<OverloadMethodDescriptor>();
	private Stack<MethodFrame> frames = new Stack<MethodFrame>();
	private CompilationUnit unit;
	public static class OverloadMethodDescriptor implements Comparable<OverloadMethodDescriptor>{
		String resource;
		String method;
		public OverloadMethodDescriptor(String qn, String realName) {
			super();
			this.resource = qn;
			this.method = realName;
		}
		@Override
		public int compareTo(OverloadMethodDescriptor o) {
			int qn_comp = this.resource.compareTo(o.resource);
			if (qn_comp == 0){
				return this.method.compareTo(o.method);
			}else{
				return qn_comp;
			}
		}
		public String getResource() {
			return resource;
		}
		public String getMethod() {
			return method;
		}
	}
	public void reset() {
		descriptors.clear();
	}
	public OverloadMethodDescriptor[] getDescriptors() {
		Collections.sort(descriptors);
		return descriptors.toArray(new OverloadMethodDescriptor[descriptors.size()]);
	}
	@Override
	public void endVisit(CompilationUnit node) {
		unit = null;
	}

	@Override
	public void endVisit(MethodDeclaration node) {
	/*	if (node.isConstructor()) {
			List parameters = node.parameters();
			String[] tn = new String[parameters.size()];
			int ct = 0;
			for (Object svd : parameters){
				tn[ct++] = Util.formatType(((SingleVariableDeclaration)svd).getType());				
			}
			counters.peek().addNewConstructor(tn);
		}*/
	}
	
	@Override
	public boolean visit(CompilationUnit node) {
		unit = node;
		return true;
	}

	@Override
	public boolean visit(MethodDeclaration node) {
		if (!countable(node)) return false;
		frames.peek().addMethod(node);
		return false;
	}

	private boolean countable(MethodDeclaration node) {
		int flags = node.getModifiers();
		if ((flags & IModifierConstants.ACC_STATIC) != 0)
			return false;
		return true;
	}
	@Override
	public void endVisit(TypeDeclaration node) {
		/*if (node.isInterface()) return;
		ConstructorCounter cc = counters.pop();
		if (cc.entries.size() > 1){
			descriptors.put(cc.qn, new ConstructorDescriptor(cc.entries));
		}*/
		MethodFrame mf = frames.pop();
		for (MethodGroup mg : mf.groups){
			if (mg.methods.size() > 1){
				rename(mg);
			}
		}
	}

	private void rename(MethodGroup mg) {
		for (int i = 0; i < mg.methods.size(); i++){
			MethodDeclaration md = mg.methods.get(i);
			IMethodBinding binding = md.resolveBinding();
			if(binding == null) continue;
			String qn = binding.getDeclaringClass().getQualifiedName();
			StringBuffer sb = new StringBuffer();
			sb.append(binding.getName()).append('(');
			ITypeBinding[] pns = binding.getParameterTypes();
			for (ITypeBinding p : pns){
				sb.append(p.getQualifiedName()).append(',');
			}
			sb.append(')');
			descriptors.add(new OverloadMethodDescriptor(qn,sb.toString()));
			
		}
	}
	@Override
	public boolean visit(TypeDeclaration node) {
		/*if (node.isInterface()) return false; 
		String typeName = unit.getPackage().getName().getFullyQualifiedName()
				+ "." + node.getName().getIdentifier();
		counters.push(new ConstructorCounter(typeName));*/
		frames.push(new MethodFrame(node));
		List all = node.bodyDeclarations();
		for (Object bd : all){
			if (bd instanceof MethodDeclaration){
				((MethodDeclaration)bd).accept(this);
			}
		}
		return false;
	}

}
